/**
 * Main class for the bus route simulation. This uses a passenger object but not a bus object because it takes less memory to
 * just have buses be an array of deploy times than to make an array of bus objects. 
 * 
 *  Written by Jack Yuan 
 *  jly2104@columbia.edu
 *  
 *  COMSW3134 Assignment 1: Bus Route Simulation
 *  This code can be freely used, copied and manipulated except for COMSW3134
 **/
import java.io.*;
public class Main {
	public static void main(String [] args){
		try{
			/**
			 * First check if there are enough parameters to run program
			 * If not, quit program and have user restart program
			 **/
			if(args.length != 4){
				System.out.println("Not enough arguments at startup, please run the program again.");
				System.exit(0);
			}
			/**
			 * There are a correct number of arguments and now we test if the arguments are valid.
			 * Convert all arguments to integers first and check if integers fall within proper values. 
			 * If not integers, program falls to the catch block.
			 **/
			boolean validInput = true;
			int numpass = new Integer(args[0]).intValue();
			int numstops = new Integer(args[1]).intValue();
			int interval = new Integer(args[2]).intValue();
			int mode = new Integer(args[3]).intValue();
			
			if(numpass <1 || numstops <2 || interval <1 || mode != 1 && mode != 2){
				validInput = false;
			}
			
			/**
			 * Again check if validInput. If input is not valid then quit the program and force user to run the program again
			 * with proper arguments
			 **/
			
			if(!validInput){
				System.out.println("You have invalid inputs, please run the program again.");
				System.exit(0);
			}
			/**
			 * Input is valid and the program will start.
			 * First set whether demo mode or simulation mode using a boolean.
			 * Boolean demoMode is set against the mode variable.
			 * If it is demo mode then the program runs only once and if not then the program runs 1000 times
			 **/
			else{ 
				boolean demoMode = false;
				if(mode == 1){demoMode = true;}
				
				int runTimes = 0;
				
				if(demoMode){runTimes = 1;}				
				else{runTimes = 1000;}
				
				System.out.println("Simulating bus line on "+numpass+" passenger(s), "+numstops+" stop(s), buses every "+interval+" minute(s) apart. ("+(int)1440/interval+" buses a day)");
				double totalTimeAllTrials = 0.00000; //totalTimeAllTrials is the sum of the average times from each of the 1000 trials that are run
				
				/**
				 * This for loop encompasses all of the code to be run in the program if there was no 
				 * previous errors, since the only difference between the demo mode and simulation
				 * mode is what is printed on the screen and how many times the program is run. This 
				 * allows minimal copy paste of code using a few if statements within to check print
				 * lines for demo mode or not. 
				 **/
				
				for(int q = 0; q < runTimes; q++){					
					int numBusses = (int)(1440/interval); //numBusses determines how many busses will be needed by dividing how many minutes there are in a day by the interval busses arrive
					int[] busses = new int[numBusses];
					double totalWaitTime = 0.0; //total wait time will sum up all of the wait times for the passengers throughout each trial
					
					/**
					 * This for loop is to set the time of departure for each bus in the array of busses.
					 * This is done by taking interval and multiplying by the index i which also determines which bus it is.
					 * The display of when the bus arrives is also printed here and is calculated by dividing the minutes by 60 for hours and 
					 * by taking the modulus of minutes by 60 for the minutes and then concatenating them together to form a time string.
					 * This string is made and printed only if it is demo mode. 
					 **/
					
					for(int i =0; i< busses.length; i++){
						busses[i] = i*interval;						
						if(demoMode){
							String str = "Bus " + (i+1)+" leaves at ";
							if(busses[i]/60 < 10){str += "0"+(busses[i]/60);}
							else{str += (busses[i]/60);}
							if(busses[i]%60 < 10){str += ":0" + (busses[i]%60)+".";}
							else{str += ":"+(busses[i]%60)+".";}						
							System.out.println(str);
						}
					}
					
					/**
					 * This starts the creation of passengers and setting all the values that make up
					 * a passenger. This portion of the code will also calculate the earliest time a passenger
					 * will be at a stop and the latest time a passenger will be at a stop. All the calculations
					 * including the time the passenger arrives at whichever stop, which stop they want to get off,
					 * which bus to take, when they are picked up and the wait time calculation is all in this portion
					 * of code. 
					 **/
					
					Passenger[] passengers = new Passenger[numpass];
					String earliesttime, latesttime; //These two strings are used to represent the earliest and latest times passengers will be arriving at any stop
					earliesttime = "";
					/**
					 * Earliest time is calculated in that the earliest passenger should arrive when the first bus of the day reaches it's final stop
					 * and so the first passenger should arrive no earlier than however many stops there are. The latest passenger should arrive when
					 * the final bus is deployed and the value is taken directly from the final element of the busses array. 
					 **/
					if(numstops > 60){
						if(numstops < 600){
							if((numstops)%60 < 10)
								earliesttime += "0"+((int)(numstops)/60)+":0"+(int)((numstops)%60);
							else
								earliesttime += "0"+((int)(numstops)/60)+":"+(int)((numstops)%60);
						}
						else{
							if((numstops)%60 < 10)
								earliesttime += ""+((int)(numstops)/60)+":0"+(int)((numstops)%60);
							else
								earliesttime += ""+((int)(numstops)/60)+":"+(int)((numstops)%60);
						}
					}
					else{					
						if((numstops)%60 < 10)
							earliesttime += "0"+((int)(numstops)/60)+":0"+(int)((numstops)%60);
						else
							earliesttime += ""+((int)(numstops)/60)+":"+(int)((numstops)%60);				
					}
					latesttime = ""+busses[busses.length-1]/60+":"+busses[busses.length-1]%60;
					
					/**
					 * Print the earliest a passenger will arrive and the latest time on the first time this loop runs.
					 * If it is simulation mode then print the second line stating that 1000 trials will be run.
					 * Also if it simulation mode print a | to measure progress every 10 trials is run. 
					 **/
					
					if(q == 0){
						System.out.println("The earliest a passenger will present is "+earliesttime+", the latest "+latesttime+".");
						if(!demoMode)
							System.out.println("Running full simulation over 1000 trials");
					}
					if(q%10 == 0 && !demoMode && q != 0)
						System.out.print("|");
					
					/**
					 * This loop fully sets up the the passengers array, giving every passenger a stop to be at,
					 * a stop to go to, calculate their wait time, calculate which bus they will be taking, and 
					 * summing up their overall wait time if it is simulation mode
					 **/
					
					for(int i = 0; i< numpass; i++){
						int stopnum = ((int)(Math.random()*numstops)); //stopnum is which stop the passenger is getting on at, randomly generated
						/**
						 * The time at stop is calculated by taking a random number that can range from when the first bus 
						 * reaches the last stop and when the last bus is deployed.
						 **/
						int timeAtStop = ((int)((Math.random()*(busses[busses.length-1]-numstops))+(numstops)));
						/**
						 * This checks to make sure that the time at stop is before the last bus being deployed
						 * or the time at stop will have a number subtracted to ensure that it is before when the last bus is deployed.
						 * With the time at stop and which stop calculated a new passenger object is instantiated taking in parameters
						 * for what stop, the time at the stop and which passenger this is.
						 **/
						if(timeAtStop > busses[busses.length-1]){timeAtStop -= 2*(timeAtStop-busses[busses.length-1]);}
						passengers[i] = new Passenger(stopnum,timeAtStop,i+1);
						/**
						 * I calculated which bus to take by taking the time that the passenger arrived at the stop, subtracting it from whichever stop
						 * the passenger was at, and then dividing whatever that value was by the interval the busses travel. By casting this as an integer
						 * the number has its decimal points dropped and this will give the index to whichever bus will be deployed and reach that stop in the
						 * least amount of time. 
						 **/
						int busToTake = ((int)(timeAtStop-passengers[i].getStop())/interval);
						/**
						 * Calculating the wait time is done by taking the time the passenger arrived at the stop and subtracting it from the
						 * sum of when the bus to take was deployed and the stop that the passenger is at. 
						 **/
						passengers[i].setWaitTime(timeAtStop-(busses[busToTake]+passengers[i].getStop()));
						totalWaitTime += timeAtStop-(busses[busToTake]+passengers[i].getStop()); // totalWaitTime will sum this wait time with all previous and latter wait times of this trial
						passengers[i].setBustTake(busses[busToTake]); // this sets a field for which bus to take in the passenger class with the deploy time of the bus
						/**
						 * Generating when the passenger gets off is then accomplished by taking the passenger's stop and subtracting the total number of stops.
						 * That gives the range for the random number generator which is also added to the original passenger's stop so that the stop they get 
						 * off at will be after the stop they got on at. This may present a problem if the passenger gets on at the last stop because then the only
						 * possible stop that the passenger can then get off at is also the last stop. This may be changed later by setting the stops to wrap around
						 * and have the passenger go back to the beginning or go backwards from the last stop. 
						 **/
						passengers[i].setStopOff(passengers[i].getStop() + (int)(Math.random()*(numstops - (passengers[i].getStop()+1))+1)); 
						if(demoMode)
							System.out.println(passengers[i]); // this line will only be called if we are running in demo mode and invokes the to string method of the passenger object
					}	
					totalTimeAllTrials += (double)totalWaitTime/(double)numpass; // summing the totalTimeALLTrials to the totalWaitTime divided by number of passengers
					if(mode == 1){
					    System.out.println("The average wait of a passenger for a bus is: "+totalTimeAllTrials+" minutes");
					}
				}
				double averageWaitTime = totalTimeAllTrials/1000.00; // average wait time of 1000 trials by taking the average of the average wait time for 1000 trials
				if(!demoMode)
					System.out.println("\nAverage wait over 1000 trials: "+averageWaitTime+" minutes"); //this line is printed in simulation mode only, displaying the average wait time over 1000 trials.
				
				/**
				 *This code was used to print a CSV file of data points for the graph.  
				 ***/				
				/*	if(mode == 2){
					File output = new File("output.txt");
					FileOutputStream fout = new FileOutputStream(output,true);
					new PrintStream(fout).append((1440/interval)+","+averageWaitTime+"\n");
					fout.close();
				}*/
			}
		}catch(Exception e) //catch exception where input is not an integer
		{
			/**
			 * Give users an error message and quit the program to force the user to restart the program using the correct parameters.
			 **/
			System.out.println("You have entered parameters that are not integers. Please rerun the program using corect parameters.");
			System.exit(0);
		}
	}
}
